package com.hrbxlh.virtualization.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.NetUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.hrbxlh.virtualization.constant.*;
import com.hrbxlh.virtualization.dto.ContainerCreateDTO;
import com.hrbxlh.virtualization.dto.FlavorDTO;
import com.hrbxlh.virtualization.enums.ContainerStatusEnum;
import com.hrbxlh.virtualization.exception.GlobalException;
import com.hrbxlh.virtualization.pojo.Container;
import com.hrbxlh.virtualization.result.ResultEnum;
import com.hrbxlh.virtualization.service.DockerService;
import com.hrbxlh.virtualization.util.SSHUtil;
import com.jayway.jsonpath.JsonPath;
import net.minidev.json.JSONArray;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.http.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author cheer
 */
@Service
public class DockerServiceImpl implements DockerService, ApplicationListener<ContextRefreshedEvent> {

    private Logger logger = LoggerFactory.getLogger(DockerServiceImpl.class);

    @Autowired
    private RestTemplate restTemplate;

    /**
     * Rancher的环境id，使用的环境在rancher.properties中配置 env 变量
     */
    private String envId;

    /**
     * 存储Rancher主机与主机Ip地址的对应关系
     */
    private Map<String, String> hostToIpMap = new ConcurrentHashMap<>();

    @Override
    public Container createContainer(String imageName, String volumeName, FlavorDTO flavorDTO, String hostname) {
        imageName = RegistryConstant.REPOSITORY_PREFIX + "/" + imageName;

        // 创建卷目录
        if (StringUtils.isNotBlank(volumeName)) {
            FileUtil.mkdir(VolumeConstant.NFS_DIR + "/" + volumeName);
        }

        if (flavorDTO == null) {
            flavorDTO = new FlavorDTO(2, 8);
        }

        String rancherId = createContainer(imageName, volumeName, flavorDTO.getCpu(), flavorDTO.getRam(), hostname);

        return queryContainer(rancherId);
    }

    private String createContainer(String imageName, String volumeName, int cpu, int ram, String hostname) {
        try {
            String url = String.format(RancherConstant.CREATE_CONTAINER_URL, envId);
            ContainerCreateDTO containerCreateDTO = new ContainerCreateDTO(imageName, volumeName, cpu, ram, hostname);
            String requestBody = JSON.toJSONString(containerCreateDTO, SerializerFeature.WriteMapNullValue);
            String responseBody = restTemplate.postForObject(url, requestBody, String.class);
            return JsonPath.read(responseBody, "$.id");
        } catch (Exception e) {
            logger.error("创建容器失败：" + e.getMessage(), e);
            throw new GlobalException(ResultEnum.CONTAINER_CREATE_ERROR);
        }
    }

    private Container queryContainer(String rancherId) {
        Container container = new Container();
        container.setRancherId(rancherId);

        // 设置超时60秒
        int retryCount = 0;
        int maxRetryCount = 600;

        try {
            String url = String.format(RancherConstant.GET_CONTAINER_URL, envId, rancherId);
            do {
                String responseBody = restTemplate.getForObject(url, String.class);
                String dockerId = JsonPath.read(responseBody, "$.externalId");
                String ip = JsonPath.read(responseBody, "$.primaryIpAddress");

                if (dockerId != null && ip != null) {
                    container.setDockerId(dockerId);
                    container.setIp((int) NetUtil.ipv4ToLong(ip));
                    return container;
                }

                // 如果docker id或者ip还未生成，则1秒钟后再读取
                Thread.sleep(100);
                retryCount++;

            } while (retryCount < maxRetryCount);

        } catch (Exception e) {
            logger.error("查询容器 {} 失败：" + e.getMessage(), rancherId, e);
        }

        if (retryCount == maxRetryCount) {
            logger.error("查询容器 {} 超时", rancherId);
        }

        throw new GlobalException(ResultEnum.CONTAINER_QUERY_ERROR);
    }

    @Override
    public void deleteContainer(String rancherId) {
        if (!isContainerExists(rancherId)) {
            return;
        }

        try {
            String url = String.format(RancherConstant.DELETE_CONTAINER_URL, envId, rancherId);
            restTemplate.delete(url);
        } catch (Exception e) {
            logger.error("删除容器 {} 失败：" + e.getMessage(), rancherId, e);
            // throw new GlobalException(ResultEnum.CONTAINER_DELETE_ERROR);
        }
    }

    @Override
    public boolean isContainerExists(String rancherId) {
        try {
            String url = String.format(RancherConstant.GET_CONTAINER_URL, envId, rancherId);
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            return responseEntity.getStatusCode() != HttpStatus.NOT_FOUND;
        } catch (Exception e) {
            if (StringUtils.equals(DockerConstant.NOT_FOUND, e.getMessage())) {
                return false;
            }
            throw new GlobalException(ResultEnum.DOCKER_ERROR);
        }
    }

    @Override
    public void startContainer(String rancherId) {
        if (!isContainerExists(rancherId)) {
            throw new GlobalException(ResultEnum.CONTAINER_ERROR);
        }

        try {
            while (true) {
                ContainerStatusEnum containerStatusEnum = queryContainerStatus(rancherId);
                if (containerStatusEnum == ContainerStatusEnum.RUNNING || containerStatusEnum == ContainerStatusEnum.STARTING) {
                    return;
                } else if (containerStatusEnum == ContainerStatusEnum.STOPPING) {
                    Thread.sleep(1000);
                } else {
                    break;
                }
            }

            String url = String.format(RancherConstant.START_CONTAINER_URL, envId, rancherId);
            restTemplate.postForEntity(url, null, String.class);
        } catch (Exception e) {

            logger.error("启动容器 {} 失败：" + e.getMessage(), rancherId, e);
            throw new GlobalException(ResultEnum.CONTAINER_START_ERROR);
        }
    }

    @Override
    public void stopContainer(String rancherId) {
        try {
            ContainerStatusEnum containerStatusEnum = queryContainerStatus(rancherId);
            if (containerStatusEnum != ContainerStatusEnum.RUNNING) {
                return;
            }

            String url = String.format(RancherConstant.STOP_CONTAINER_URL, envId, rancherId);
            restTemplate.postForEntity(url, null, String.class);
        } catch (Exception e) {
            logger.error("停止容器 {} 失败：" + e.getMessage(), rancherId, e);
            throw new GlobalException(ResultEnum.CONTAINER_STOP_ERROR);
        }
    }

    private ContainerStatusEnum queryContainerStatus(String rancherId) {
        if (!isContainerExists(rancherId)) {
            throw new GlobalException(ResultEnum.CONTAINER_ERROR);
        }

        try {
            String url = String.format(RancherConstant.GET_CONTAINER_URL, envId, rancherId);
            String responseBody = restTemplate.getForObject(url, String.class);
            String status = JsonPath.read(responseBody, "$.state");
            return ContainerStatusEnum.getByNameIgnoreCase(status);
        } catch (Exception e) {
            logger.error("查询容器 {} 状态失败：" + e.getMessage(), rancherId, e);
            throw new GlobalException(ResultEnum.CONTAINER_QUERY_ERROR);
        }
    }

    @Override
    public String queryDesktopUrl(String rancherId) {
        try {
            Container container = queryContainer(rancherId);
            String host = queryContainerHost(rancherId);

            initHostIfNotExists(host);
            writeToken(rancherId, host, NetUtil.longToIpv4(container.getIp()));
            return String.format(NovncConstant.NOVNC_URL, hostToIpMap.get(host), rancherId);
        } catch (Exception e) {
            logger.error("查询容器 {} 桌面地址失败：" + e.getMessage(), rancherId, e);
            throw new GlobalException(ResultEnum.DESKTOP_QUERY_ERROR);
        }
    }

    private void initHostIfNotExists(String host) {
        if (!hostToIpMap.containsKey(host)) {
            initHost();
            if (!hostToIpMap.containsKey(host)) {
                logger.error("容器主机 {} 不存在", host);
                throw new GlobalException(ResultEnum.DOCKER_ERROR);
            }
        }
    }

    private void writeToken(String rancherId, String host, String ip) {
        host = hostToIpMap.get(host);
        String url = String.format("http://%s:56000/novnc?rancherId=%s&ip=%s", host, rancherId, ip);
        try {
            restTemplate.getForObject(url, Void.class);
        } catch (Exception e) {
            logger.error("容器 {} 写入token文件失败：" + e.getMessage(), rancherId, e);
            throw new GlobalException(ResultEnum.DOCKER_ERROR);
        }
    }

    @Override
    public String queryClipboardUrl(String rancherId) {
//        // 设置超时10秒
//        int retryCount = 0;
//        int maxRetryCount = 10;
//
//        try {
//            do {
//                ContainerServiceAddress address = queryClipboardAddress(rancherId);
//                String host = address.getHost();
//                String port = address.getPort();
//
//                if (host != null && port != null) {
//                    initHostIfNotExists(host);
//                    return String.format(RancherConstant.CLIPBOARD_URL, hostToIpMap.get(host), port);
//                }
//
//                // 如果host或者port还未生成，则1秒后再读取
//                Thread.sleep(1000);
//                retryCount++;
//
//            } while (retryCount < maxRetryCount);
//
//        } catch (Exception e) {
//            logger.error("查询容器 {} 剪切板地址失败：" + e.getMessage(), rancherId, e);
//        }
//
//        if (retryCount == maxRetryCount) {
//            logger.error("查询容器 {} 剪切板地址失败", rancherId);
//        }

        throw new GlobalException(ResultEnum.CLIPBOARD_ERROR);
    }

    @Override
    public String queryFtpUrl(String rancherId) {
//        // 设置超时10秒
//        int retryCount = 0;
//        int maxRetryCount = 10;
//
//        try {
//            do {
//                ContainerServiceAddress address = queryFtpAddress(rancherId);
//                String host = address.getHost();
//                String port = address.getPort();
//
//                if (host != null && port != null) {
//                    initHostIfNotExists(host);
//                    return String.format(RancherConstant.FTP_URL, hostToIpMap.get(host), port);
//                }
//
//                // 如果host或者port还未生成，则1秒后再读取
//                Thread.sleep(1000);
//                retryCount++;
//
//            } while (retryCount < maxRetryCount);
//
//        } catch (Exception e) {
//            logger.error("查询容器 {} FTP地址失败：" + e.getMessage(), rancherId, e);
//        }
//
//        if (retryCount == maxRetryCount) {
//            logger.error("查询容器 {} FTP地址失败", rancherId);
//        }

        throw new GlobalException(ResultEnum.FTP_ERROR);
    }

    @Override
    public String queryHost(String rancherId) {
        String host = queryContainerHost(rancherId);
        initHostIfNotExists(host);
        return hostToIpMap.get(host);
    }

    private ContainerServiceAddress queryVncAddress(String rancherId) {
        return buildContainerServiceAddress(rancherId, RancherConstant.VNC_PORT);
    }

    private ContainerServiceAddress queryClipboardAddress(String rancherId) {
        return buildContainerServiceAddress(rancherId, RancherConstant.CLIPBOARD_PORT);
    }

    private ContainerServiceAddress queryFtpAddress(String rancherId) {
        return buildContainerServiceAddress(rancherId, RancherConstant.FTP_PORT);
    }

    private ContainerServiceAddress buildContainerServiceAddress(String rancherId, String portPattern) {
        ContainerServiceAddress address = new ContainerServiceAddress();

        String url = String.format(RancherConstant.GET_CONTAINER_URL, envId, rancherId);
        String responseBody = restTemplate.getForObject(url, String.class);

        address.setHost(JsonPath.read(responseBody, "$.hostId"));
        JSONArray ports = JsonPath.read(responseBody, "$.ports[0:]");
        for (Object o : ports) {
            String portStr = (String) o;
            if (StringUtils.contains(portStr, portPattern)
                    && portStr.length() > portPattern.length()) {
                address.setPort(StringUtils.substringBetween(portStr, ":", ":"));
            }
        }

        return address;
    }

    static class ContainerServiceAddress {

        String host;

        String port;

        public String getHost() {
            return host;
        }

        public void setHost(String host) {
            this.host = host;
        }

        public String getPort() {
            return port;
        }

        public void setPort(String port) {
            this.port = port;
        }
    }

    @Override
    public void createImage(Container container, String imageName) {
        imageName = RegistryConstant.REPOSITORY_PREFIX + "/" + imageName;

        String host = queryContainerHost(container.getRancherId());
        initHostIfNotExists(host);

        commitImage(hostToIpMap.get(host), container.getDockerId(), imageName);
        pushImage(hostToIpMap.get(host), imageName);
    }

    private String queryContainerHost(String rancherId) {
        try {
            String url = String.format(RancherConstant.GET_CONTAINER_URL, envId, rancherId);
            String responseBody = restTemplate.getForObject(url, String.class);
            return JsonPath.read(responseBody, "$.hostId");
        } catch (Exception e) {
            logger.error("查询容器 {} 失败：" + e.getMessage(), rancherId, e);
            throw new GlobalException(ResultEnum.DOCKER_ERROR);
        }
    }

    private void commitImage(String hostIp, String dockerId, String imageName) {
        try {
            String url = String.format(DockerConstant.COMMIT_IMAGE_URL, hostIp, dockerId, imageName);
            restTemplate.postForEntity(url, null, String.class);
        } catch (Exception e) {
            logger.error("主机Ip {} 容器Id {} 构建镜像失败：" + e.getMessage(), hostIp, dockerId, e);
            throw new GlobalException(ResultEnum.IMAGE_COMMIT_ERROR);
        }
    }

    private void pushImage(String hostIp, String imageName) {
        try {
            String url = String.format(DockerConstant.PUSH_IMAGE_URL, hostIp, imageName);
            // 上传镜像操作需要http header携带认证信息
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add("X-Registry-Auth", DockerConstant.MOCK_USER);
            HttpEntity requestEntity = new HttpEntity(httpHeaders);
            restTemplate.postForEntity(url, requestEntity, String.class);
        } catch (Exception e) {
            logger.error("主机Ip {} 镜像 {} push失败：" + e.getMessage(), hostIp, imageName, e);
            throw new GlobalException(ResultEnum.IMAGE_PUSH_ERROR);
        }
    }

    @Override
    public void deleteImage(String imageName) {
        imageName = RegistryConstant.REPOSITORY_PREFIX + "/" + imageName;

        deleteImageOnEveryHost(imageName);

        deleteImageOnRegistry(imageName);
    }

    private void deleteImageOnEveryHost(String imageName) {
        for (String hostIp : hostToIpMap.values()) {
            try {
                String url = String.format(DockerConstant.DELETE_IMAGE_URL, hostIp, imageName);
                restTemplate.delete(url);
            } catch (Exception e) {
                // 如果docker api返回404，表示镜像不存在，这种情况下也将其视为删除成功
                if (StringUtils.equals(DockerConstant.NOT_FOUND, e.getMessage())) {
                    return;
                }
                logger.error("主机Ip {} 镜像 {} 删除失败：" + e.getMessage(), imageName, e);
                throw new GlobalException(ResultEnum.IMAGE_DELETE_ERROR.fillArgs("镜像无法被删除"));
            }
        }
    }

    private void deleteImageOnRegistry(String imageName) {
        String digest = getDigest(imageName);
        if (digest == null) {
            return;
        }

        try {
            String url = String.format(RegistryConstant.DELETE_IMAGE_URL, RegistryConstant.REPOSITORY_PREFIX, imageName,
                    digest);
            restTemplate.delete(url);
        } catch (Exception e) {
            if (StringUtils.equals(DockerConstant.NOT_FOUND, e.getMessage())) {
                return;
            }
            logger.error("镜像仓库删除镜像 {} 失败" + e.getMessage(), imageName, e);
            throw new GlobalException(ResultEnum.IMAGE_REPOSITORY_ERROR);
        }
    }

    private String getDigest(String imageName) {
        try {
            String url = String.format(RegistryConstant.GET_DIGEST_URL, RegistryConstant.REPOSITORY_PREFIX, imageName);
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add(HttpHeaders.ACCEPT, RegistryConstant.MANIFESTS_ACCEPT);
            HttpEntity requestEntity = new HttpEntity(httpHeaders);
            ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.HEAD, requestEntity, String.class);
            List<String> headers = responseEntity.getHeaders().get(RegistryConstant.DIGEST);
            if (CollectionUtils.isNotEmpty(headers)) {
                return headers.get(0);
            }
        } catch (Exception e) {
            if (StringUtils.equals(DockerConstant.NOT_FOUND, e.getMessage())) {
                return null;
            }
            logger.error("查询镜像 {} digest失败：", imageName, e);
            throw new GlobalException(ResultEnum.IMAGE_REPOSITORY_ERROR);
        }
        return null;
    }

    @Override
    public void execCmd(Container container, String cmd) {
        String host = queryContainerHost(container.getRancherId());
        initHostIfNotExists(host);

        String execId = createExec(hostToIpMap.get(host), container.getDockerId(), cmd);
        startExec(host, execId, container.getDockerId(), cmd);
    }

    @Override
    public void execCmd(String hostIp, String dockerId, String cmd) {
        String execId = createExec(hostIp, dockerId, cmd);
        startExec(hostIp, execId, dockerId, cmd);
    }


    private String createExec(String hostIp, String dockerId, String cmd) {
        try {
            String url = String.format(DockerConstant.EXEC_CREATE_URL, hostIp, dockerId);

            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
            String requestBody = String.format(DockerConstant.EXEC_CREATE_REQUEST_BODY, cmd);
            HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders);

            ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
            return JsonPath.read(responseEntity.getBody(), "$.Id");
        } catch (Exception e) {
            logger.error("主机 {} 容器 {} 创建命令 {} 失败：" + e.getMessage(), hostIp, dockerId, cmd, e);
            throw new GlobalException(ResultEnum.CONTAINER_CMD_ERROR);
        }
    }

    private void startExec(String hostIp, String execId, String dockerId, String cmd) {
        try {
            String url = String.format(DockerConstant.EXEC_START_URL, hostIp, execId);

            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
            HttpEntity requestEntity = new HttpEntity<>(DockerConstant.EXEC_START_REQUEST_BODY, httpHeaders);

            ResponseEntity responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
            logger.info("输出结果：{}", responseEntity.getBody());
        } catch (Exception e) {
            logger.error("主机 {} 容器 {} 执行命令 {} 失败：", hostIp, dockerId, cmd, e);
            throw new GlobalException(ResultEnum.CONTAINER_CMD_ERROR);
        }
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext().getParent() == null) {
            initEnv();
            initHost();
        }
    }

    /**
     * 读取Rancher环境Id
     */
    private void initEnv() {
        try {
            String responseBody = restTemplate.getForObject(RancherConstant.LIST_PROJECTS_URL, String.class);
            List<String> envIdList = JsonPath.read(responseBody, RancherConstant.ENV_ID_JSON_PATH);
            if (CollectionUtils.isNotEmpty(envIdList)) {
                envId = envIdList.get(0);
            } else {
                logger.error("Docker环境异常：Rancher环境配置错误");
                new Thread(() -> {
                    while (true) {
                        String response = restTemplate.getForObject(RancherConstant.LIST_PROJECTS_URL, String.class);
                        List<String> envIds = JsonPath.read(response, RancherConstant.ENV_ID_JSON_PATH);
                        if (CollectionUtils.isNotEmpty(envIds)) {
                            envId = envIds.get(0);
                        }
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (Exception e) {
            logger.error("Docker环境异常：" + e.getMessage(), e);
        }
    }

    /**
     * 读取所有主机ip
     */
    private void initHost() {
        try {
            String url = String.format(RancherConstant.LIST_HOSTS_URL, envId);
            String responseBody = restTemplate.getForObject(url, String.class);
            JSONArray hosts = JsonPath.read(responseBody, "$.data[0:]");
            for (Object o : hosts) {
                LinkedHashMap host = (LinkedHashMap) o;
                hostToIpMap.put((String) host.get("id"), (String) host.get("agentIpAddress"));
            }
        } catch (Exception e) {
            logger.error("Docker环境异常：" + e.getMessage(), e);
        }
    }
}
